Repository navigation
fix: refresh the runtime JWT on demand instead of waiting for the next GetProject poll - #10096
Open
nishantmonu51 wants to merge 4 commits into
Open
nishantmonu51 wants to merge 4 commits into
nishantmonu51 wants to merge 4 commits into
Conversation
…t GetProject poll A request that found its JWT expired waited passively for up to 60s for the 15-minute GetProject refetch and then failed with "Timed out waiting for a fresh JWT" whenever a laptop sleep, a network drop or a throttled tab had skipped that refetch. Expiry was also derived from when the client first saw the token string, so a token handed over from the query cache was credited with a full TTL and the runtime rejected it with "jwt is expired". - RuntimeClient takes a JWT refresher; a request that finds the token stale triggers it right away, shares one in-flight refresh, retries with backoff while the issuer is unreachable and fails fast on 401/403 - Expiry comes from the token's exp claim (capped at its lifetime from receipt), falling back to the default TTL for tokens without claims - A runtime "jwt is expired" rejection refreshes once and retries the request - RuntimeProvider refreshes a stale token when the tab becomes visible, gains focus or the network comes back - The project and edit layouts wire a refresher that refetches GetProject, the bearer-token GetProject on public URLs or the View As credentials - GetProject stops polling after a 401/403 instead of repeating it forever
Follow-ups from review of the on-demand JWT refresh: - A refresh attempt pins the refresher and the auth context it serves before the backoff sleep and revalidates both after waking and after the call, so a user token refreshed while View As switched the context is never applied - Each refresher call is abandoned after 20s with its AbortSignal aborted (the admin refresher cancels the GetProject query so the retry is not deduped into the stalled request), waiting requests are bounded by their own 60s deadline, and updateJwt() or dispose() wakes them immediately instead of the 50ms poll - Refreshers are kept per auth context: the provider's stays registered for its context while the editor's View As registers one that reissues its mock token through GetDeployment, and the user's applies again when View As ends
…lection The edit layout forwarded every GetProject JWT to RuntimeProvider with the "user" context, so any user-token update that landed while View As was active (the 15-minute poll, a focus refetch, or an on-demand refresh that was still in flight when View As started) replaced the mock token and the preview showed the user's data under View As. - The layout derives the provider's jwt, authContext and refreshJwt from the View As stores (editSessionRuntimeAuth): the mock user's JWT and a refresher that reissues it through GetDeployment while a mock user is selected, the user's otherwise - createUpdateEditSessionDevJWT no longer registers the refresher itself; the layout owns it through the provider's props - A regression plays the layout and provider through an in-flight user refresh that completes after View As started and checks the mock credentials survive it
# Conflicts: # web-admin/src/routes/[organization]/[project]/+layout.svelte
AdityaHegde
requested changes
Nov 9, 2026
AdityaHegde
left a comment
Collaborator
There was a problem hiding this comment.
Feels like we are doing a lot of manual refreshing, something that comes with tanstack query.
Maybe there is a path to replace while of refreshing with it with retry hook triggered by queryClient.refetch in interceptors? One issue might be circular dependancy that needs some workaround.
| return { promise, resolve }; | ||
| } | ||
|
|
||
| function sleep(ms: number): Promise<void> { |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runtime requests in Rill Cloud failed with
Timed out waiting for a fresh JWTor the runtime'sjwt is expiredafter a laptop sleep, a network drop or a throttled tab. The 30-minute runtime JWT was only refreshed by the 15-minuteGetProjectpoll, so when a poll was missed, every request waited passively for 60 seconds and then failed together until a reload. Expiry was also counted from when the client first saw the token, so a token handed over from the query cache was credited a full lifetime.Steps to reproduce
admin.rilldata.com/v1/orgs/*/projects/*, so token refreshes fail while runtime requests still go through.main, requests wait for the next scheduled poll, so after 60 seconds every panel fails withTimed out waiting for a fresh JWT(some withrpc error: code = Unauthenticated desc = jwt is expired) until a reload. With this change, the first request fetches a fresh token and the panels load.What the fix does
RuntimeClienttakes a JWT refresher. A request that finds its token stale refreshes right away; concurrent requests share one in-flight refresh, which retries with backoff while the admin service is unreachable and fails fast on 401 or 403.expclaim, capped at its lifetime from receipt, with the default TTL for tokens without claims.jwt is expiredrejection refreshes once and retries the request.RuntimeProviderrefreshes a stale token when the tab becomes visible, regains focus or comes back online.AbortSignalaborted, waiting requests keep their own 60-second deadline, andupdateJwt()ordispose()wakes them at once instead of the 50 ms poll.GetProject(the bearer-tokenGetProjecton public URLs). The edit layout derives the provider's JWT, auth context and refresher from the View As stores, so a user token refresh that lands while View As is active cannot replace the mock user's token.GetProjectstops polling after a 401 or 403.Notes for review
updateEditSessionDevJWT.spec.tsplays the layout and provider through an in-flight user refresh that completes after View As starts.GetProjectrefetch fails #10095 on adjacent imports in the project layout; rebase whichever merges second.Checklist: